#ifndef OPTFRAME_NSSEQVRPSWAP2_1_HPP_
#define OPTFRAME_NSSEQVRPSWAP2_1_HPP_

// Framework includes
#include "../OptFrame/Move.hpp"
#include "../OptFrame/NSSeq.hpp"

#include <algorithm>

using namespace std;

// Working structure: vector<T>

class MoveVRPSwap2_1: public Move<RepPRV, MemPRVP>
{

protected:
	int r1, r2; // route 1 and route 2;
	int c1, c2; // position 1 and position 2 of the clients, respectively
	bool reverse; // true -> reverse moviment
	PRVPProblemInstance& pPRVP;

public:

	MoveVRPSwap2_1(PRVPProblemInstance& _pPRVP, int _r1, int _r2, int _c1, int _c2, bool _reverse) :
		pPRVP(_pPRVP), r1(_r1), r2(_r2), c1(_c1), c2(_c2), reverse(_reverse)
	{
	}

	virtual ~MoveVRPSwap2_1()
	{
	}

	bool get_reverse()
	{
		return reverse;
	}

	int get_r1()
	{
		return r1;
	}

	int get_r2()
	{
		return r2;
	}

	int get_c1()
	{
		return c1;
	}

	int get_c2()
	{
		return c2;
	}

	bool canBeApplied(const RepPRV& rep)
	{
		bool all_positive = (r1 >= 0) && (r2 >= 0) && (c1 >= 0) && (c2 >= 0);
		return all_positive && (rep.size() >= 2) && (rep.at(r1).size() >= 2) && (rep.at(r2).size() > 0);
	}

	virtual MoveVRPSwap2_1& apply(RepPRV& rep)
	{
		int aux;

		if (reverse)
		{
			aux = rep.at(r1).at(c1);
			rep.at(r1).at(c1) = rep.at(r1).at(c1 + 1);
			rep.at(r1).at(c1 + 1) = aux;
		}

		//swapping
		aux = rep.at(r1).at(c1);
		rep.at(r1).at(c1) = rep.at(r2).at(c2);
		rep.at(r2).at(c2) = aux;

		rep.at(r2).insert(rep.at(r2).begin() + c2 + 1, rep.at(r1).at(c1 + 1));
		rep.at(r1).erase(rep.at(r1).begin() + c1 + 1);

		// p->p, r1->r2, r2->r1, c1->c2, c2->c1, reverse->reverse,
		return *new MoveVRPSwap2_1(pPRVP, r2, r1, c2, c1, reverse);
	}

	MoveVRPSwap2_1& apply(MemPRVP& m, RepPRV& rep)
	{
		//getchar();
		Matrix<double>& dist = (*pPRVP.dist);

		double cargaC1, cargaC11, cargaC2;
		cargaC1 = pPRVP.dados->at(rep.at(r1).at(c1)).at(DEMANDA);
		cargaC11 = pPRVP.dados->at(rep.at(r1).at(c1 + 1)).at(DEMANDA);
		cargaC2 = pPRVP.dados->at(rep.at(r2).at(c2)).at(DEMANDA);

		int aux;

		//calculando a carga ocupada nas rotas
		double cargaR1 = 0, cargaR2 = 0;
		//pegando as cargas da memória
		cargaR1 = m.getRouteLoad().at(r1);
		cargaR2 = m.getRouteLoad().at(r2);

		double inf_antes = min(0.0, pPRVP.capacidade - cargaR1) + min(0.0, pPRVP.capacidade - cargaR2);
		double inf_dps = max(0.0, (cargaR1 - cargaC1 - cargaC11 + cargaC2) - pPRVP.capacidade) + max(0.0, (cargaR2 - cargaC2 + cargaC1 + cargaC11) - pPRVP.capacidade);

		//calculo do custo do movimento
		double delta = 0;
		double delta_inf = inf_antes + inf_dps;

		//para facilitar as contas, adicionando o depósito no final das rotas
		rep.at(r1).push_back(0);
		rep.at(r2).push_back(0);
		//remocao dos arcos antigos
		if (c1 - 1 < 0)//deve considerar o depósito
			delta += -dist(0, rep.at(r1).at(c1)) - dist(rep.at(r1).at(c1 + 1), rep.at(r1).at(c1 + 2));
		else
			delta += -dist(rep.at(r1).at(c1 - 1), rep.at(r1).at(c1)) - dist(rep.at(r1).at(c1 + 1), rep.at(r1).at(c1 + 2));

		if (c2 - 1 < 0)//deve considerar o depósito
			delta += -dist(0, rep.at(r2).at(c2)) - dist(rep.at(r2).at(c2), rep.at(r2).at(c2 + 1));
		else
			delta += -dist(rep.at(r2).at(c2 - 1), rep.at(r2).at(c2)) - dist(rep.at(r2).at(c2), rep.at(r2).at(c2 + 1));

		if (reverse)
		{
			aux = rep.at(r1).at(c1);
			rep.at(r1).at(c1) = rep.at(r1).at(c1 + 1);
			rep.at(r1).at(c1 + 1) = aux;
		}

		//adicionando os novos arcos
		if (c2 - 1 < 0)
			delta += dist(0, rep.at(r1).at(c1)) + dist(rep.at(r1).at(c1 + 1), rep.at(r2).at(c2 + 1));
		else
			delta += dist(rep.at(r2).at(c2 - 1), rep.at(r1).at(c1)) + dist(rep.at(r1).at(c1 + 1), rep.at(r2).at(c2 + 1));

		if(c1 -1 < 0)
			delta += dist(0, rep.at(r2).at(c2)) + dist(rep.at(r2).at(c2), rep.at(r1).at(c1 + 2));
		else
			delta += dist(rep.at(r1).at(c1 - 1), rep.at(r2).at(c2)) + dist(rep.at(r2).at(c2), rep.at(r1).at(c1 + 2));

		//swapping
		aux = rep.at(r1).at(c1);
		rep.at(r1).at(c1) = rep.at(r2).at(c2);
		rep.at(r2).at(c2) = aux;

		rep.at(r2).insert(rep.at(r2).begin() + c2 + 1, rep.at(r1).at(c1 + 1));
		rep.at(r1).erase(rep.at(r1).begin() + c1 + 1);

		//removendo os depósitos que facilitaram a conta
		rep.at(r1).pop_back();
		rep.at(r2).pop_back();
		//setando as variações de fo e inviabilidade
		m.setDelta(delta);
		m.setDelta_inf(delta_inf);
		//atualizando as cargas
		m.getRouteLoad().at(r1) = cargaR1 - cargaC1 - cargaC11 + cargaC2;
		m.getRouteLoad().at(r2) = cargaR2 - cargaC2 + cargaC1 + cargaC11;

		// p->p, r1->r2, r2->r1, c1->c2, c2->c1, reverse->reverse,
		return *new MoveVRPSwap2_1(pPRVP, r2, r1, c2, c1, reverse);
	}
	virtual bool operator==(const Move<RepPRV, MemPRVP>& _m) const
	{
		const MoveVRPSwap2_1& m1 = (const MoveVRPSwap2_1&) _m;
		return ((m1.r1 == r1) && (m1.r2 == r2) && (m1.c1 == c1) && (m1.c2 == c2) && (m1.reverse == reverse));
	}

	void print() const
	{
		cout << "MoveVRPSwap2_1( ";
		cout << r1 << ", " << r2 << ", " << c1 << ", " << c2 << ", " << reverse << " )";
		cout << endl;
	}
};

class NSIteratorVRPSwap2_1: public NSIterator<RepPRV, MemPRVP>
{

private:
	PRVPProblemInstance& pPRVP;
	MoveVRPSwap2_1* m;
	vector<MoveVRPSwap2_1*> moves;
	int index; //index of moves
	const RepPRV& r;

public:

	NSIteratorVRPSwap2_1(PRVPProblemInstance& _pPRVP, const RepPRV& _r) :
		pPRVP(_pPRVP), r(_r)
	{
		m = NULL;
		index = 0;
	}

	virtual ~NSIteratorVRPSwap2_1()
	{
		/*for (int i = 0; i < moves.size(); i++)
			delete moves[i];*/
	}

	void first()
	{

		for (int r1 = 0; r1 < r.size(); r1++)
		{
			if (r.at(r1).size() >= 2)
			{
				for (int r2 = 0; r2 < r.size(); r2++)
				{
					if (r1 != r2)
					{
						for (int c1 = 0; c1 < ((int)r.at(r1).size()) - 1; c1++)
						{
							for (int c2 = 0; c2 < r.at(r2).size(); c2++)
							{
								moves.push_back(new MoveVRPSwap2_1(pPRVP, r1, r2, c1, c2, false));//normal
								moves.push_back(new MoveVRPSwap2_1(pPRVP, r1, r2, c1, c2, true));//reverse
							}
						}
					}
				}
			}
		}
		if (moves.size() > 0)
		{
			m = moves[index];
		}
		else
			m = NULL;
	}

	void next()
	{
		index++;
		if (index < moves.size())
		{
			m = moves[index];
		}
		else
			m = NULL;
	}

	bool isDone()
	{
		return m == NULL;
	}

	MoveVRPSwap2_1& current()
	{
		if (isDone())
		{
			cout << "There isnt any current element!" << endl;
			cout << "NSSeqVRPSwap2_1. Aborting." << endl;
			exit(1);
		}

		return *m;
	}
};

class NSSeqVRPSwap2_1: public NSSeq<RepPRV, MemPRVP>
{

private:
	PRVPProblemInstance& pPRVP;

public:

	NSSeqVRPSwap2_1(PRVPProblemInstance& _pPRVP) :
		pPRVP(_pPRVP)
	{
	}

	virtual ~NSSeqVRPSwap2_1()
	{
	}

	MoveVRPSwap2_1& move(const RepPRV& rep)
	{
		if (rep.size() < 2)
			return *new MoveVRPSwap2_1(pPRVP, -1, -1, -1, -1, false);

		int r1 = rand() % rep.size();

		if (rep.at(r1).size() < 2)
			return *new MoveVRPSwap2_1(pPRVP, -1, -1, -1, -1, false);

		int r2;

		do
		{
			r2 = rand() % rep.size();
		} while (r1 == r2);

		if (rep.at(r2).size() < 1)
			return *new MoveVRPSwap2_1(pPRVP, -1, -1, -1, -1, false);

		int c1 = rand() % (rep.at(r1).size() - 1);
		int c2 = rand() % rep.at(r2).size();

		bool reverse = rand() % 2;

		return *new MoveVRPSwap2_1(pPRVP, r1, r2, c1, c2, reverse);
	}

	virtual NSIteratorVRPSwap2_1& getIterator(const RepPRV& r)
	{
		return *new NSIteratorVRPSwap2_1(pPRVP, r);
	}

	virtual void print()
	{
		cout << "NSSeqVRPSwap2_1" << endl;
	}
};

#endif /*OPTFRAME_NSSEQVRPSWAP2_1_HPP_*/
